---
title: Migrating to Apollo Kotlin 5
subtitle: Step-by-step guide on migrating from Apollo Kotlin 4
description: Learn how to migrate from version 4 with key changes, new features, and tools designed for improved stability and maintainability. Follow step-by-step guidance and make a seamless transition
---

Apollo Kotlin 5 is an incremental evolution of Apollo Kotlin 4 and keeps the same package name. 

In most cases, bumping the version should be transparent. The exceptions are:

- Symbols that were `DeprecationLevel.ERROR` in v4 are now removed. Remove all your deprecated usages before migrating to v5.
- `apollo-compiler` is still considered experimental. You will need to update your [Apollo Compiler Plugins](https://www.apollographql.com/docs/kotlin/advanced/compiler-plugins). 

Read below for more details.

## `apollo-gradle-plugin-external`

The Apollo Gradle Plugin [now uses classloader isolation](https://github.com/apollographql/apollo-kotlin/pull/6524) and does not use R8 to relocate dependencies anymore. As a result, the `apollo-gradle-plugin-external` artifact and the `com.apollographql.apollo.external` plugins are not used anymore and now point to an empty plugin. You should use `apollo-gradle-plugin` and `com.apollographql.apollo` instead:

```kotlin
// Replace
implementation("com.apollographql.apollo:apollo-gradle-plugin-external:4.3.3")
// With 
implementation("com.apollographql.apollo:apollo-gradle-plugin:4.3.3")

plugins {
  // Replace
  id("com.apollographql.apollo.external")
  // With 
  id("com.apollographql.apollo")
}
```

## `apollo-gradle-plugin`

### `operationOutputGenerator` and `operationIdGenerator` are removed.

While running your `OperationOutputGenerator` directly in your build script classpath was convenient, it required the compiler code to run completely in the global buildscript classpath. This created many issues such as incompatible dependencies and/or unneeded build invalidations.

The v5 Apollo Gradle plugin runs the Apollo compiler in isolated classloaders, meaning generating the ids needs to happen in that same classloader using the `ServiceLoader` API. 

To do so, use `ApolloCompilerPlugin`:

```kotlin
class MyPlugin : ApolloCompilerPlugin {
  override fun beforeCompilationStep(
      environment: ApolloCompilerPluginEnvironment,
      registry: ApolloCompilerRegistry,
  ) {
    registry.registerOperationIdsGenerator {
      it.map { OperationId(it.source.md5(), it.name) }
    }
  }
}
```

Read more in the [persisted queries](https://www.apollographql.com/docs/kotlin/v5/advanced/persisted-queries) and [compiler plugins](https://www.apollographql.com/docs/kotlin/v5/advanced/compiler-plugins) pages.

### `downloadApolloSchema` is removed

Apollo Kotlin 4 allowed downloading a schema with a `downloadApolloSchema` Gradle task. This task is now removed.

For single-shot downloads of a schema during development, use the [Apollo Kotlin CLI](https://github.com/apollographql/apollo-kotlin-cli). It is much faster (no Gradle configuration), comes with interactive help and soon autocomplete scripts.

Alternatively, configure [your introspection block](https://www.apollographql.com/docs/kotlin/advanced/plugin-recipes#downloading-a-schema). Doing so documents how to update the schema for other developers and makes it easy to update the schema without passing extra arguments.

```kotlin 
apollo {
  service("service") {
    packageName.set("com.example")

    // This creates a downloadServiceApolloSchemaFromIntrospection task
    introspection {
      endpointUrl.set("https://example.com/graphql/endpoint")
      // The path is interpreted relative to the current project
      schemaFile.set(file("src/main/graphql/schema.graphqls"))
    }
  }
}

// You can create a shorthand lifecycle task name
tasks.register("downloadSchema") {
  dependsOn("downloadServiceApolloSchemaFromIntrospection")
}
```
## `apollo-idling-resource` 

Apollo Kotlin 5 removes the `apollo-idling-resource` artifact. Usage of `ApolloIdlingResource` has been decreasing, and better alternatives for testing are now available.

For a good overview of alternative solutions, we recommend reading [this article from Jose Alcérreca](https://medium.com/androiddevelopers/alternatives-to-idling-resources-in-compose-tests-8ae71f9fc473).

## `apollo-compiler` 

### `@nonnull` is an error

Apollo Kotlin 4 had a `@nonnull` client directive to force generating fields as non-null.

Since `@nonnull`, we've worked hard with the [nullability working group](https://github.com/graphql/nullability-wg/) to improve the handling of null types in GraphQL.

As part of this effort, it was recognized that the nullability information belongs to the schema. Fields that are only nullable for error reasons can now be marked with `@semanticNonNull`:

```graphql
type User {
  email: String @semanticNonNull
}

# or if you don't own the schema, use extensions
extend type User @semanticNonNullField(name: "email")
```

The client can then decide how to handle errors with `@catch`:

```graphql
query GetUser {
  user {
    # generated as `String?` (current default)
    email @catch(to: NULL)
    # generated as `Result<String, Error>`
    email @catch(to: RESULT)
    # generated as `String`, throws if there is an error
    email @catch(to: THROW)
  }
}
```

You can read more in the ["handling nullability" page](https://www.apollographql.com/docs/kotlin/advanced/nullability).


## `apollo-runtime`

### New WebSockets

In Apollo Kotlin 5, the WebSocket code has been rewritten to simplify it and clarify the error and retry semantics. 

All the classes in the `com.apollographql.apollo.network.ws` package have been deprecated and a new implementation is available in `com.apollographql.apollo.network.websocket`. 

To migrate, replace all your instances of `com.apollographql.apollo.network.ws` with `com.apollographql.apollo.network.websocket`:

```kotlin
// Replace 
import com.apollographql.apollo.network.ws.AppSyncWsProtocol
import com.apollographql.apollo.network.ws.WebSocketNetworkTransport

// With
import com.apollographql.apollo.network.websocket.AppSyncWsProtocol
import com.apollographql.apollo.network.websocket.WebSocketNetworkTransport
```

Some shorthand methods on `ApolloClient.Builder` have been deprecated and replaced by explicit configuration. In those cases, you can use `subscriptionNetworkTransport` directly.

For an example, you can replace `ApolloClient.Builder.webSocketEngine()` as follows:

```kotlin
// Replace
ApolloClient.Builder()
    .webSocketEngine(webSocketEngine) 
    .build()
// With
ApolloClient.Builder()
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .webSocketEngine(webSocketEngine)
            .build()
    )
    .build()
```

The default WebSocket protocol has changed to [graphql-ws](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md).

If you were already using it before, you may remove the call to `protocol()` or define it explicitly using `subscriptionNetworkTransport()`:

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
    .protocol(GraphQLWsProtocol.Factory())
    .build()
    
// With
val apolloClient = ApolloClient.Builder()
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .serverUrl(url)
            .wsProtocol(GraphQLWsProtocol())
            .build()
    )
    .build()

```

If you are still relying on [the (now deprecated) transport](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md), you can use `SubscriptionWsProtocol`:

```kotlin
val apolloClient = ApolloClient.Builder()
    .subscriptionNetworkTransport(
        WebSocketNetworkTransport.Builder()
            .serverUrl(url)
            .wsProtocol(SubscriptionsWsProtocol())
            .build()
    )
    .build()
```

The retry management is now moved to `retryOnErrorInterceptor`:   

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
    .webSocketServerUrl("http://localhost:8080/subscriptions")
    .webSocketReopenWhen { e, attempt ->
      delay(2.0.pow(attempt.toDouble()).toLong())
      // retry after the delay
      true
    }

// With
val apolloClient = ApolloClient.Builder()
    .webSocketServerUrl("http://localhost:8080/subscriptions")
    .retryOnErrorInterceptor(RetryOnErrorInterceptor { context ->
      if (context.request.operation is Subscription<*>) {
        delay(2.0.pow(context.attempt.toDouble()).toLong())
        true
      } else {
        false
      }
    })
```

## `apollo-http-cache`

`apollo-http-cache` is now deprecated. Instead, it uses the existing OkHttp cache using [cacheUrlOverride](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-request/-builder/cache-url-override.html).

You can remove the `apollo-http-cache` artifact:

```kotlin
dependencies {
  // Remove
  implementation("com.apollographql.apollo:apollo-http-cache:4.3.3")
}
```

Configure your `ApolloClient` with a cache-enabled `OkHttpClient` and set `enablePostCaching` to true:

```kotlin
// Replace
val apolloClient = ApolloClient.Builder()
    .serverUrl(mockServer.url())
    .httpCache(directory = "http_cache", maxSize = 10_000_000)
    .build()

// With
val apolloClient = ApolloClient.Builder()
    .networkTransport(
        HttpNetworkTransport.Builder()
            // Enable POST caching
            .httpRequestComposer(DefaultHttpRequestComposer(serverUrl = mockServer.url(), enablePostCaching = true))
            .httpEngine(
                DefaultHttpEngine {
                  OkHttpClient.Builder()
                      // Make sure to use a different directory for your cache as the format changed
                      .cache(directory = File(application.cacheDir, "http_cache2"), maxSize = 10_000_000)
                      .build()
                }
            )
            .build()
    )
    .build()
```

The OkHttp cache uses the standard `Cache-Control` HTTP header. Compared to Apollo Kotlin 4 and `HttpFetchPolicy` the semantics are different but you can map most of the concepts:

**CacheFirst**:
```kotlin
// Replace 
apolloClient.query(query).httpFetchPolicy(HttpFetchPolicy.CacheFirst)

// With
apolloClient.query(query) // no need to add a cache-control header, this is the default
```

**CacheOnly**:
```kotlin
// Replace 
apolloClient.query(query).httpFetchPolicy(HttpFetchPolicy.CacheOnly)

// With
apolloClient.query(query).addHttpHeader("cache-control", "only-if-cached")
```

**NetworkOnly**:
```kotlin
// Replace
apolloClient.query(query).httpFetchPolicy(HttpFetchPolicy.CacheOnly)

// With
apolloClient.query(query).addHttpHeader("cache-control", "no-cache")
```

We believe this new caching scheme is simpler and more aligned with the rest of the ecosystem, but there are important differences with the previous scheme:

- There is no equivalent to `NetworkFirst` and/or `httpExpireTimeout()`.
- The cache keys have changed, meaning your cache will be invalidated.

If either of these limitations is important for your use case, please [open an issue](https://github.com/apollographql/apollo-kotlin/issues/new). 